(function() {
    if (!("MetawebSuite" in window)) {
        var mw = window.MetawebSuite = {
            system: {
                xhtmlNamespace:             "http://www.w3.org/1999/xhtml",
                _loadingPackages:           [],
                _packageLoadingCallbacks:   [],
                _compiledPackages:          {}
            }
        };
        
        mw.system.log = ("console" in window) ? function(o) { window.console.log(o); } : function(){};
        
        mw.system.parseURLParameters = function(url, to, types) {
            to = to || {};
            types = types || {};
            
            if (typeof url == "undefined") {
                url = location.href;
            }
            var q = url.indexOf("?");
            if (q < 0) {
                return to;
            }
            url = (url+"#").slice(q+1, url.indexOf("#")); // toss the URL fragment
            
            var params = url.split("&"), param, parsed = {};
            var decode = window.decodeURIComponent || unescape;
            for (var i = 0; param = params[i]; i++) {
                var eq = param.indexOf("=");
                var name = decode(param.slice(0,eq));
                var old = parsed[name];
                if (typeof old == "undefined") {
                    old = [];
                } else if (!(old instanceof Array)) {
                    old = [old];
                }
                parsed[name] = old.concat(decode(param.slice(eq+1)));
            }
            for (var i in parsed) {
                if (!parsed.hasOwnProperty(i)) continue;
                var type = types[i] || String;
                var data = parsed[i];
                if (!(data instanceof Array)) {
                    data = [data];
                }
                if (type === Boolean && data[0] == "false") {
                    to[i] = false; // because Boolean("false") === true
                } else {
                    to[i] = type.apply(this, data);
                }
            }
            return to;
        };
        mw.system.parseScriptX = function(doc, urlFragment, to, types) {
            to = to || {};
            try {
                var script = mw.system.findScriptX(doc, urlFragment);
                if (script != null) {
                    var url = script.src;
                    
                    var x = url.indexOf(urlFragment);
                    to.baseUrl = url.substr(0, x);
                    if (to.baseUrl.charAt(to.baseUrl.length - 1) != "/") {
                        to.baseUrl = to.baseUrl + "/";
                    }
                    
                    mw.system.parseURLParameters(url, to, types);
                }
            } catch (e) {}
            return to;
        };
        mw.system.parseScript = function(urlFragment, to, types) {
            return mw.system.parseScriptX(document, urlFragment, to, types);
        };

        /*
         *  Primitive code loading functionality
         */
        
        mw.system.getHeadX = function(doc) {
            var heads = doc.getElementsByTagName("head");
            if (heads.length == 0) {
                heads = doc.getElementsByTagNameNS(mw.system.xhtmlNamespace, "head");
            }
            return heads.length > 0 ? heads[0] : null;
        };
        mw.system.getHead = function() {
            return mw.system.getHeadX(document);
        };
        
        mw.system.findScriptX = function(doc, substring) {
            var scripts = doc.getElementsByTagName("script");
            if (scripts.length == 0) {
                scripts = doc.getElementsByTagNameNS(mw.system.xhtmlNamespace, "script");
            }
            for (var i = 0; i < scripts.length; i++) {
                var script = scripts[i];
                var url = script.src;
                if (url.indexOf(substring) >= 0) {
                    return script;
                }
            }
            return null;
        };
        mw.system.findScript = function(substring) {
            return mw.system.findScriptX(document, substring);
        };
        
        mw.system.addScriptX = function(doc, url, onError, charset) {
            onError = (typeof onError == "string") ? onError : "";
            if (doc.body == null) {
                try {
                    var q = onError.replace(/'/g, '"');
                    doc.write(
                        "<script src='" + url + "' onerror='" + q + "'" + (charset ? (" charset='" + charset + "'") : "") +
                              " type='text/javascript'>" + onError + "</script>");
                    return;
                } catch (e) {
                    // fall through
                }
            }

            var script = doc.createElement("script");
            if (onError) {
                try { script.innerHTML = onError; } catch(e) {}
                script.setAttribute("onerror", onError);
            }
            if (charset) {
                script.setAttribute("charset", charset);
            }
            script.type = "text/javascript";
            script.language = "JavaScript";
            script.src = url;
            
            mw.system.getHead(doc).appendChild(script);
            return script;
        };
        mw.system.addScript = function(url, onError, charset) {
            return mw.system.addScriptX(document, url, onError, charset);
        };
        mw.system.addScriptsX = function(doc, prefix, urls, charset) {
            for (var i = 0; i < urls.length; i++) {
                var url = prefix + urls[i];
                mw.system.addScriptX(doc, url, "mw.system.logDebug('error loading " + url + "');", charset);
            }
        };
        mw.system.addScripts = function(prefix, urls, charset) {
            mw.system.addScriptsX(document, prefix, urls, charset);
        };
        
        mw.system.addCssX = function(doc, url, charset) {
            if (doc.body == null) {
                try {
                    doc.write("<link rel='stylesheet' href='" + url + "'" + (charset ? (" charset='" + charset + "'") : "") + " type='text/css'/>");
                    return;
                } catch (e) {
                    // fall through
                }
            }
            
            var link = doc.createElement("link");
            link.setAttribute("rel", "stylesheet");
            link.setAttribute("type", "text/css");
            link.setAttribute("href", url);
            if (charset) {
                link.setAttribute("charset", charset);
            }
            
            mw.system.getHead(doc).appendChild(link);
            return link;
        };
        mw.system.addCss = function(url, charset) {
            return mw.system.addCssX(document, url, charset);
        };
        mw.system.addCssesX = function(doc, prefix, urls, charset) {
            for (var i = 0; i < urls.length; i++) {
                var url = prefix + urls[i];
                mw.system.addCssX(doc, url);
            }
        };
        mw.system.addCsses = function(prefix, urls, charset) {
            mw.system.addCssesX(document, prefix, urls, charset);
        };
        
        mw.system.addCallback = function(f) {
            var id = "cb" + new Date().getTime() + "x" + Math.floor(Math.random() * 1000000);
            window[id] = f;
            
            return id;
        };
        mw.system.removeCallback = function(id) {
            delete window[id];
        };
        
        /*
         *  Package loading functionality
         */
        
        mw.system._removeLoadingPackage = function(doc, packageName) {
            var loadingPackages = mw.system._loadingPackages;
            for (var i = loadingPackages.length - 1; i >= 0; i--) {
                var loadingPackage = loadingPackages[i];
                if (loadingPackage.name == packageName) {
                    loadingPackages.splice(i, 1);
                    
                    if ("cbID" in loadingPackage) {
                        mw.system.removeCallback(loadingPackage.cbID);
                    }
                    return loadingPackage;
                }
            }
            return null;
        };
        mw.system._checkLoadingPackageCallbacks = function(doc) {
            if (mw.system._loadingPackages.length == 0 && mw.system._packageLoadingCallbacks.length > 0) {
                var callbacks = mw.system._packageLoadingCallbacks;
                mw.system._packageLoadingCallbacks = [];
                
                window.setTimeout(function() {
                    for (var i = 0; i < callbacks.length; i++) {
                        try {
                            callbacks[i]();
                        } catch (e) {}
                    }
                }, 0);
            }
        };
        mw.system.loadPackageX = function(doc, packageName, url, onError, charset) {
            var cbID = mw.system.addCallback(function() {
                mw.system._removeLoadingPackage(doc, packageName);
                mw.system._checkLoadingPackageCallbacks(doc);
                if (typeof onError == "function") {
                    onError();
                } else if (typeof onError == "string") {
                    eval(onError);
                }
            });
            
            mw.system._loadingPackages.push({
                name:   packageName,
                cbID:   cbID
            });
            
            return mw.system.addScriptX(doc, url, cbID + "()", charset);
        };
        mw.system.loadPackage = function(packageName, url) {
            return mw.system.loadPackageX(document, packageName, url);
        };
        mw.system.loadNativePackage = function(packageShortName) {
            mw.system.loadPackage("metaweb-suite-" + packageShortName, 
                mw.system.nativePackageUrl + packageShortName + "/" + packageShortName + "-package.js");
        };
        mw.system.onStartLoadingPackageX = function(doc, packageName) {
            var loadingPackages = mw.system._loadingPackages;
            for (var i = loadingPackages.length - 1; i >= 0; i--) {
                var loadingPackage = loadingPackages[i];
                if (loadingPackage.name == packageName) {
                    return;
                }
            }
            mw.system._loadingPackages.push({
                name: packageName
            });
        };
        mw.system.onStartLoadingPackage = function(packageName) {
            mw.system.onStartLoadingPackageX(document, packageName);
        };
        mw.system.onStartLoadingNativePackage = function(packageShortName, scripts, csses, custom) {
            var packageFullName = "metaweb-suite-" + packageShortName;

            var mw = window.MetawebSuite;
            mw.system.onStartLoadingPackage(packageFullName);
            
            if (packageShortName in mw) {
                var pkg = mw[packageShortName];
            } else {
                var pkg = mw[packageShortName] = {};
            }
            if (!("params" in pkg)) {
                pkg.params = {};
            }
            if (mw.system.isPackageCompiled(packageFullName)) {
                pkg.params.baseUrl = mw.system.nativePackageUrl + packageShortName + "/";
            } else {
                mw.system.parseScript("/" + packageShortName + "/" + packageShortName + "-package.js", pkg.params);
                pkg.params.baseUrl += packageShortName + "/";
            }
            
            if (typeof custom == "function") {
                try {
                    custom();
                } catch (e) {
                    mw.system.exception(e);
                    return;
                }
            }
            
            if (!mw.system.isPackageCompiled(packageFullName)) {
                var bundle = pkg.params.baseUrl + "bundle/" + packageShortName + "-bundle";
                if (scripts.length > 0) {
                    if (mw.system.debug) {
                        mw.system.addScripts(pkg.params.baseUrl + "scripts/", scripts);
                    } else {
                        mw.system.addScript(bundle + ".js", null, "utf-8");
                    }
                }
                if (csses.length > 0) {
                    if (mw.system.debug) {
                        mw.system.addCsses(pkg.params.baseUrl + "styles/", csses);
                    } else {
                        mw.system.addCss(bundle + ".css");
                    }
                }
            }
        };
        mw.system.onFinishLoadingPackageX = function(doc, packageName) {
            mw.system._removeLoadingPackage(doc, packageName);
            mw.system._checkLoadingPackageCallbacks(doc);
            mw.system.logDebug("Loaded package " + packageName);
        };
        mw.system.onFinishLoadingPackage = function(packageName) {
            mw.system.onFinishLoadingPackageX(document, packageName);
        };
        mw.system.runWhenAllPackagesLoaded = function(f) {
            if (mw.system._loadingPackages.length > 0) {
                mw.system._loadingPackages.push(f);
            } else {
                window.setTimeout(f, 0);
            }
        };
        
        /*
         *  Compilation functionality
         */
        mw.system.flagPackageAsCompiled = function(packageName) {
            mw.system._compiledPackages[packageName] = true;
        };
        mw.system.isPackageCompiled = function(packageName) {
            return packageName in mw.system._compiledPackages;
        }
        
        if (!("mw" in window)) {
            window.mw = mw;
        }
        
        var paramTypes = { debug: Boolean, autoLoad: Boolean };
        
        mw.system.urlParams = mw.system.parseURLParameters(
            document.location.href, 
            ("metawebSuite_params" in window) ? window.metawebSuite_params : { debug: false, autoLoad: true, compilation: null }, 
            paramTypes
        );
        mw.system.urlParams = mw.system.parseScript("/mw.js", mw.system.urlParams, paramTypes);
            /*
             *  It is important to let the parameters of mw.js override the parameters
             *  of the web page's URL. This is so that the web page's author can lock
             *  certain crucial parameters if desired, e.g., which freebase instance and
             *  which timestamp. Otherwise, by playing with the web page's URL, anyone
             *  can make a web page appear to "say" anything with a different freebase
             *  instance or a different timestamp.
             */
        
        mw.system.baseUrl = mw.system.urlParams.baseUrl || "";
        mw.system.nativePackageUrl = mw.system.baseUrl + "packages/";
        
        mw.system.debug = mw.system.urlParams.debug;
        mw.system.logDebug = mw.system.debug ? mw.system.log : function(o) {};
        mw.system.exception = mw.system.debug ? function(e, msg) { mw.system.log(e); } : function(e, msg) {};
        
        if (mw.system.urlParams.autoLoad) {
            if (mw.system.urlParams.compilation != null) {
                var compilationPath = mw.system.baseUrl + "compilations/" + mw.system.urlParams.compilation;
                if (mw.system.debug) {
                    mw.system.addScript(compilationPath + "-debug.js", null, "utf-8");
                } else {
                    mw.system.addScript(compilationPath + ".js", null, "utf-8");
                    mw.system.addCss(compilationPath + ".css", "utf-8");
                }
            } else {
                mw.system.loadNativePackage("core");
                mw.system.loadNativePackage("ui");
                mw.system.loadNativePackage("freebase");
            }
        }
    }
})();